// Application implementation for Gtk+
// Copyright 2006 (C) Ralph Thomas

#include <toolkit/application.h>
#include <util/resources.h>
#include <vector>
#include <algorithm>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/filesystem/operations.hpp>
#include <gtk/gtk.h>
#include <iostream>
#include <stdexcept>
#include <binreloc.h>
#include <libgnomevfs/gnome-vfs.h>

namespace bfs = boost::filesystem;

namespace toolkit {
	struct file_monitor_t {
		file_monitor_t() : monitor_m( 0 ) {}
		~file_monitor_t() {
			if( monitor_m != 0 ) gnome_vfs_monitor_cancel(monitor_m);
		}
		GnomeVFSMonitorHandle*						monitor_m;
		boost::signal<void (const std::string&)>	signal_m;
	};
	struct monitor_data_t {
		file_monitor_t*				monitor_m;
		boost::signals::connection	connection_m;

		monitor_data_t() : monitor_m( 0 ) {}
		//
		// defined below, after the definition of applicationImpl.
		//
		~monitor_data_t();
	};
		
	class applicationImpl : public application {
		typedef std::vector<thread_function_t>		threadFuncList;
		typedef boost::recursive_mutex::scoped_lock	lock;
		typedef boost::recursive_mutex			mutex;
		typedef std::map<std::string, file_monitor_t*> file_monitor_map_t;
		
		threadFuncList	_functions;
		mutex		_mutex;
		GtkIconFactory*	_stockIcons;
		file_monitor_map_t	_monitors;
		//
		/// Gtk+ idle entry point.
		//
		static gboolean on_idle(gpointer data) {
			applicationImpl* impl = static_cast<applicationImpl*>(data);
			if (!impl) return false;
			impl->callIdlers();
			return false;
		}
		//
		/// File monitor callback.
		//
		static void file_changed(GnomeVFSMonitorHandle *handle,
                                 const gchar *monitor_uri,
                                 const gchar *info_uri,
                                 GnomeVFSMonitorEventType event_type,
                                 gpointer user_data)
		{
			file_monitor_t* fm = static_cast<file_monitor_t*>( user_data );
			if (!fm) return;
			fm->signal_m(info_uri);
		}
		//
		/// This function will call all of the idle functions.
		//
		void callIdlers() {
			//
			// Call of the thread idle functions.
			//
			threadFuncList safeFuncs;
			{
				lock lk( _mutex );
				safeFuncs = _functions;
				_functions.clear();
			}
			threadFuncList::iterator first( safeFuncs.begin() );
			threadFuncList::iterator last( safeFuncs.end() );
			while( first != last ) {
				try { (*first)(); }
				catch( std::exception& ex ) { std::cerr << "idle time exception: " << ex.what() << std::endl; }
				catch( ... ) { std::cerr << "unknown idle exception!" << std::endl; }
				first++;
			}
		}
		static void deleter(file_monitor_map_t::value_type i) {
			delete i.second;
		}
	  public:
		applicationImpl() : _stockIcons( 0 ) {}
		virtual ~applicationImpl() {
			if( _stockIcons ) {
				gtk_icon_factory_remove_default( _stockIcons );
				g_object_unref( _stockIcons );
			}
			std::for_each( _monitors.begin(), _monitors.end(), &deleter );
		}
		virtual initReturn init() {
			//
			// Initialize BinReloc so that we can correctly
			// load resource files.
			//
			BrInitError error;
			br_init( &error );
			bfs::path resource( util::getResourcePath() );
			std::string gtkrc_path( bfs::path( resource / "gtkrc" ).native_file_string() );
			//
			// Load our stock items. The gtkrc file defines which icons to use for
			// these.
			//
			GtkStockItem item;
			item.stock_id = "rotate_cw";
			item.label = "";
			item.modifier = (GdkModifierType) 0;
			item.keyval = 0;
			item.translation_domain = "";
			gtk_stock_add( &item, 1 );
			item.stock_id = "rotate_ccw";
			gtk_stock_add( &item, 1 );
			//
			// Load the custom gtkrc file.
			//
			gtk_rc_parse( gtkrc_path.c_str() );
			//
			// Bring up GNOME-VFS.
			//
			gnome_vfs_init();
			return kOk;
		}
		virtual void run() { gtk_main(); }
		virtual void quit() { gtk_main_quit(); }
		virtual void doIdle() { callIdlers(); }
		virtual void executeInMain( const thread_function_t& f ) {
			lock lk( _mutex );
			_functions.push_back( f );
			//
			// The glib main loop functions are all threadsafe, so
			// it's perfectly okay to do this.
			//
			g_idle_add( &applicationImpl::on_idle, this );
		}
		virtual monitor_connection_t monitor( const std::string& p, const file_change_function_t& f, bool directory) {
			//
			// Canonicalize the URI.
			//
			gchar* uri_c = gnome_vfs_make_uri_canonical(p.c_str());
			std::string uri(uri_c);
			g_free(uri_c);
			//
			// Do we already have a monitor set up for this one?
			//
			file_monitor_map_t::const_iterator i(_monitors.find(uri));
			if (i == _monitors.end()) {
				//
				// Start monitoring this file.
				//
				file_monitor_t* fm( new file_monitor_t() );
				gnome_vfs_monitor_add( &(fm->monitor_m), uri.c_str(), (directory ? GNOME_VFS_MONITOR_DIRECTORY : GNOME_VFS_MONITOR_FILE),
					&applicationImpl::file_changed, fm );
				_monitors[uri] = fm;
				i = _monitors.find(uri);
			}
			//
			// Connect the function to the monitor signal.
			//
			monitor_data_t* md = new monitor_data_t();
			md->monitor_m = i->second;
			md->connection_m = i->second->signal_m.connect(f);
			return monitor_connection_t(md);
		}
		void stop_monitoring(file_monitor_t* fm) {
			if (!fm) return;
			for (file_monitor_map_t::iterator i = _monitors.begin(); i != _monitors.end(); i++) {
				if (i->second == fm) {
					_monitors.erase(i);
					break;
				}
			}
			delete fm;
		}
		static applicationImpl& getInstance() {
			static applicationImpl app;
			return app;
		}
	};
	application& application::getInstance() {
		return applicationImpl::getInstance();
	}
	void application::error(const std::string& detail) {
		GtkMessageDialog* dialog(GTK_MESSAGE_DIALOG(gtk_message_dialog_new_with_markup(0, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "<b>Error</b>")));
		gtk_message_dialog_format_secondary_text(dialog, detail.c_str());
		gtk_dialog_run(GTK_DIALOG(dialog));
		g_object_unref(G_OBJECT(dialog));
	}
	application::~application() {}
 	monitor_data_t::~monitor_data_t() {
		if (connection_m.connected()) connection_m.disconnect();
		if (!monitor_m) return;
		if (!monitor_m->signal_m.num_slots()) {
			//
			// The monitor isn't monitoring anything any more.
			//
			applicationImpl::getInstance().stop_monitoring(monitor_m);
		}
	}
}

